#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/input.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <pthread.h>
// 在文件顶部添加信号量头文件
#include <semaphore.h>
#include "font.h"
 
// BMP格式头规范
struct bitmap_header
{
	int16_t type;
	int32_t size; // 图像文件大小
	int16_t reserved1;
	int16_t reserved2;
	int32_t offbits; // bmp图像数据偏移量
}__attribute__((packed));
//_attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐
struct bitmap_info
{
	int32_t size;   // 本结构大小	
	int32_t width;  // 图像宽
	int32_t height; // 图像高
	int16_t planes;
 
	int16_t bit_count; // 色深
	int32_t compression;
	int32_t size_img; // bmp数据大小，必须是4的整数倍
	int32_t X_pel;
	int32_t Y_pel;
	int32_t clrused;
	int32_t clrImportant;
}__attribute__((packed));
 
// 以下结构体不一定存在于BMP文件中，除非：
// bitmap_info.compression为真
struct rgb_quad
{
	int8_t blue;
	int8_t green;
	int8_t red;
	int8_t reserved;
}__attribute__((packed));

//触摸屏文件
#define TS_PATH  "/dev/input/event0"
//屏幕文件
#define FB_FILE  "/dev/fb0"

//触摸屏文件
int ts_fd;
//屏幕文件
int lcd_fd;

//映射文件指针
unsigned int *mem_p;
//坐标点
int X , Y ;
//线程结束和开始标志
volatile int thread_exit_flag = 0;


int lcd_init(void);
int lcd_uninit(void);
int show_bmp(const char *pathname);
int get_xy(void);
void show_one(void);
int show_bmp_xy(const char *pathname, int start_x, int start_y);
void *pthread_fun1(void *arg);
struct LcdDevice *init_lcd(const char *device);
void *pthread_fun2(void *arg);
char* getNowtime(void) ;
void *pthread_fun3(void *arg);
void show_location(void);
void show_weather(void);
void play_video(const char* filename) ;


int main(void)
{
    int n = 0; // 状态变量，用于控制程序流程
     int continueDisplay = 1; // 新增标志变量，用于控制循环
     pthread_t thread1,thread2,thread3;//线程一
    
    while (1) 
    {
        // 根据状态变量n来决定执行的流程
        switch (n) 
        {
            case 0:
                // 显示主页面
                // show_one();
                lcd_init();
                show_bmp("theme.bmp"); 
                
                while (continueDisplay)
                {
                    if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
                    {
                        perror("fail to pthread_create");
                    }
                    if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
                    {
                        perror("fail to pthread_create");
                    }
                    if(pthread_create(&thread3, NULL, pthread_fun3, NULL) != 0)
                    {
                        perror("fail to pthread_create");
                    }
                    show_location();
                    show_weather();
                    get_xy(); // 获取触摸坐标
                    // 检测是否触摸了管理员登录区域
                    if (Y < 100 && X > 650) 
                    {
                        thread_exit_flag = 1; // 设置退出标志
                        pthread_join(thread1, NULL); // 清理线程资源
                        pthread_join(thread2, NULL); // 清理线程资源
                        pthread_join(thread3, NULL); // 清理线程资源
                        continueDisplay = 0; // 设置标志变量，退出循环
                    }
                    
                }
                n = 1; // 状态变更为1
                break;
            case 1:
                // get_xy(); // 获取触摸坐标
                // 检测是否触摸了管理员登录区域
                lcd_init();
                show_bmp("password.bmp");
                lcd_uninit();
                n = 2; // 状态变更为2
                break;
            case 2:
                get_xy(); // 再次获取触摸坐标
                // 检测是否触摸了返回按钮
                if (Y < 100 && X < 100) 
                {
                    n = 0; // 返回主页面
                    thread_exit_flag = 0; // 设置退出标志
                    continueDisplay = 1;
                    break;
                }
                if ( X < 250 && Y > 410) 
                {
                    n = 3;
                    break;
                }
                break;
            case 3:
                get_xy(); // 再次获取触摸坐标
                lcd_init();
                show_bmp("user.bmp");
                lcd_uninit();
                // 检测是否触摸了返回按钮
                if (X > 200 && X < 600 && Y > 410 && Y < 460) 
                {
                    n = 0; // 返回主页面
                    thread_exit_flag = 0; // 设置退出标志
                    continueDisplay = 1;
                    break;
                }
                break;
        }
    }
}

/** 
 * 初始化Lcd
 * 
**/
struct LcdDevice *init_lcd(const char *device)
{
	//申请空间
	struct LcdDevice* lcd = malloc(sizeof(struct LcdDevice));
	if(lcd == NULL)
	{
		return NULL;
	} 

	//1打开设备
	lcd->fd = open(device, O_RDWR);
	if(lcd->fd < 0)
	{
		perror("open lcd fail");
		free(lcd);
		return NULL;
	}
	
	//映射
	lcd->mp = mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd->fd,0);

	return lcd;
}

/** 
 * 线程执行图片循环
 * 
**/
void *pthread_fun1(void *arg)
{
   while (!thread_exit_flag)
   {
        show_bmp_xy("wfl.bmp",15,150);
        sleep(3);
        show_bmp_xy("mlh.bmp",15,150);
        sleep(3);
        // 播放视频
        play_video("wfl.mp4");
        sleep(2); // 短暂休眠，避免CPU占用过高
   }

}

// 视频播放函数
void play_video(const char* filename) 
{
    FILE *fp;
    char command[256];

    // 创建管道
    system("mkfifo pipe");

    // 构建 mplayer 命令
    snprintf(command, sizeof(command), "mplayer -slave -quiet -input file=pipe -geometry 15:150 -zoom -x 600 -y 300 %s", filename);

    // 执行命令
    fp = popen(command, "w");
    if (fp == NULL) {
        perror("popen");
        exit(EXIT_FAILURE);
    }


    // 等待命令执行完成
    pclose(fp);

    // 删除管道
    system("rm pipe");
}

/** 
 * 线程执行广告词语循环
 * 
**/
void *pthread_fun2(void *arg)
{
    char *buf[] = {"天下山峰何其多，惟有此处峰成林",
                       "我在贵州等你",
                       "走遍神州大地，醉美多彩贵州",
                       "地球上一道美丽的伤疤"};
        
        //将字体写到点阵图上
    int i = 0;
    while (!thread_exit_flag)
    {
        //初始化Lcd
        struct LcdDevice* lcd = init_lcd("/dev/fb0");
                
        //打开字体	
        font *f = fontLoad("/usr/share/fonts/DroidSansFallback.ttf");
        
        //字体大小的设置
        fontSetSize(f,50);
            
        //创建一个画板（点阵图）
        bitmap *bm = createBitmapWithInit(570,50,4,getColor(0,255,255,255)); 
        //也可使用createBitmapWithInit函数，改变画板颜色
        // bitmap *bm = createBitmap(288, 100, 4);

        fontPrint(f,bm,0,0,buf[i],getColor(0,255,0,0),0);

        // 确保索引在0到3之间循环
        i = (i + 1) % 4; 
        
        //把字体框输出到LCD屏幕上
        show_font_to_lcd(lcd->mp,23,35,bm);
        
        //关闭字体，关闭画板
        fontUnload(f);
        destroyBitmap(bm);
        sleep(2); // 等待2秒
    }
    
}

void *pthread_fun3(void *arg)
{
    while (!thread_exit_flag)
    {
        //获取系统时间
        char *current_time_str = getNowtime();
        // printf("\nCurrent time: %s\n\n", current_time_str);

        //初始化Lcd
        struct LcdDevice* lcd = init_lcd("/dev/fb0");
                
        //打开字体	
        font *f = fontLoad("/usr/share/fonts/DroidSansFallback.ttf");
        
        //字体大小的设置
        fontSetSize(f,20);
            
        //创建一个画板（点阵图）
        bitmap *bm = createBitmapWithInit(138,30,4,getColor(0,255,255,255)); 
        //也可使用createBitmapWithInit函数，改变画板颜色
        // bitmap *bm = createBitmap(288, 100, 4);

        fontPrint(f,bm,0,0,current_time_str,getColor(0,255,0,0),0);
        
        //把字体框输出到LCD屏幕上
        show_font_to_lcd(lcd->mp,648,204,bm);
        
        //关闭字体，关闭画板
        fontUnload(f);
        destroyBitmap(bm);
        // 释放由getNowtime分配的内存
        free(current_time_str);
    }
    
    
}

void show_location(void)
{
    char buff[] = {"广州市从化区"};

    //初始化Lcd
    struct LcdDevice* lcd = init_lcd("/dev/fb0");
                
    //打开字体	
    font *f = fontLoad("/usr/share/fonts/DroidSansFallback.ttf");
        
    //字体大小的设置
    fontSetSize(f,25);
            
    //创建一个画板（点阵图）
    bitmap *bm = createBitmapWithInit(120,30,4,getColor(0,255,255,255)); 
        //也可使用createBitmapWithInit函数，改变画板颜色
        // bitmap *bm = createBitmap(288, 100, 4);

    fontPrint(f,bm,0,0,buff,getColor(0,255,0,0),0);

    //把字体框输出到LCD屏幕上
    show_font_to_lcd(lcd->mp,658,312,bm);
        
    //关闭字体，关闭画板
    fontUnload(f);
    destroyBitmap(bm); 
}

void show_weather(void)
{
    char buff[] = {"多云 25-34 摄氏度"};

    //初始化Lcd
    struct LcdDevice* lcd = init_lcd("/dev/fb0");
                
    //打开字体	
    font *f = fontLoad("/usr/share/fonts/DroidSansFallback.ttf");
        
    //字体大小的设置
    fontSetSize(f,20);
            
    //创建一个画板（点阵图）
    bitmap *bm = createBitmapWithInit(120,30,4,getColor(0,255,255,255)); 
        //也可使用createBitmapWithInit函数，改变画板颜色
        // bitmap *bm = createBitmap(288, 100, 4);

    fontPrint(f,bm,0,0,buff,getColor(0,255,0,0),0);

    //把字体框输出到LCD屏幕上
    show_font_to_lcd(lcd->mp,656,428,bm);
        
    //关闭字体，关闭画板
    fontUnload(f);
    destroyBitmap(bm); 
}

void show_one()
{
    //主页面
    lcd_init();
    show_bmp("theme.bmp");
    lcd_uninit();
}

/** 
 * 获取系统时间
 * 函数返回当前时间的动态分配字符串
**/
char* getNowtime(void) 
{
    time_t current_time;
    struct tm* now_time;
    char *cur_time = (char *)malloc(21 * sizeof(char)); // 分配足够的内存

    if (cur_time == NULL) 
    {
        perror("malloc failed");
        exit(EXIT_FAILURE);
    }

    time(&current_time);
    now_time = localtime(&current_time);

    // 使用strftime填充日期和时间字符串
    strftime(cur_time, 21, "%F %T", now_time); // 直接格式化为 "YYYY-MM-DD HH:MM:SS"

    return cur_time;
}


/** 
 * 初始化屏幕文件
 * 
**/
int lcd_init(void)
{
    //打开frame buffer设备文件--屏幕文件
    lcd_fd = open(FB_FILE, O_RDWR);
    if(lcd_fd == -1)
    {
        perror("open error:");
        return -1;
    }

    //屏幕映射
    mem_p = (unsigned int *)mmap(NULL, 800*480*4, PROT_READ|PROT_WRITE, MAP_SHARED, lcd_fd, 0);
    if(mem_p == MAP_FAILED)
    {
        perror("mmap");
        return -1;
    }

    return 0;
}

/** 
 * 申请内存映射内存
 * 
**/
int lcd_uninit(void)
{
   // 解除映射
    if (munmap(mem_p, 800*480*4) == -1) 
    {
        perror("munmap error:");
        return -1;
    }
    close(lcd_fd);
    return 0;
}

/** 
 * 显示一张800×480的24位bmp图片，并将图片放入内存映射空间
 * 
**/
int show_bmp(const char *pathname)
{
    int bmp_fd, i,j;
    //存储bmp文件的像素点
    unsigned char bmp_buff[800*480*3] = {0};
    //写进内存映射缓冲区的缓冲区
    unsigned int *buff = malloc(800*480*sizeof(unsigned int)); // 使用动态分配的内存

    if (buff == NULL) 
    {
        perror("malloc error:");
        return -1;
    }

    bmp_fd = open(pathname, O_RDONLY);
    if(bmp_fd == -1)
    {
        perror("open");
        free(buff);
        return -1;
    }

    //跳过54个字节头
    lseek(bmp_fd, 54, SEEK_SET);

    //读图片的全部RGB到缓冲区
    if (read(bmp_fd, bmp_buff, sizeof(bmp_buff)) != sizeof(bmp_buff)) 
    {
        perror("read error:");
        free(buff);
        close(bmp_fd);
        return -1;
    }

    //将RGB转换为BGR
    for(i=0; i<800*480; i++)
    {
        buff[i] = (bmp_buff[3*i+2] << 16) | (bmp_buff[3*i+1] << 8) | bmp_buff[3*i+0];//AGRB
    }

    // 像素点填充到屏幕
    for(i = 0; i < 480; i++) // 按行遍历
    { 
        for(j = 0; j < 800; j++) // 按列遍历
        { 
            // 从buff中获取像素数据，确保使用正确的索引
            unsigned int pixel = buff[i * 800 + j]; //i=0 :0 - 799
            
            // 将像素数据复制到帧缓冲区mem_p中
            // 确保使用正确的索引，这里假设mem_p的布局与buff相同
            mem_p[((479 - i) * 800) + j] = pixel;  //479*800 - 479*800+799
        }
    }

    free(buff);
    close(bmp_fd);
    return 0;
}

/** 
 * 获取X Y坐标值
 * 
**/
int get_xy(void)
{
    struct input_event ts;

    ts_fd = open(TS_PATH, O_RDWR);
    if(ts_fd == -1)
    {
        printf("open ts failure\n");
        return -1;
    }

    while(1)
    {
        //松开触摸后，再打印
        while (1)
        {
            read(ts_fd, &ts, sizeof(struct input_event));

            //判断类型
            if(ts.type == EV_ABS && ts.code == ABS_X)
            {
                X = ts.value;
            }

            if(ts.type == EV_ABS && ts.code == ABS_Y)
            {
                Y = ts.value;
            }

            //判断按下
            if(ts.type == EV_KEY && ts.code ==  BTN_TOUCH && ts.value == 1){ }
           
            //判断是否松开
            if(ts.type == EV_KEY && ts.code ==  BTN_TOUCH && ts.value == 0)
            {                                                  	             	
                X = X*(800.0/1024.0);      
                Y = Y*(480.0/600.0);
                break;
            }           
        }
        printf("X:%d, Y:%d\n", X, Y);
        return 0;
    }
}

/**
 * 在指定位置显示图片
 */

int show_bmp_xy(const char *pathname, int start_x, int start_y)
{
    int bmp_fd, x, y, i, j;
 
	// 读取BMP格式头，获取图片信息
	struct bitmap_header header;
	struct bitmap_info info;
	struct rgb_quad quad;
 
    bmp_fd = open(pathname, O_RDWR);
    if(bmp_fd == -1)
    {
        printf("open bmp fail\n");
        return -1;
    }   
 
 
    // 第一、二个结构体是必有信息
	read(bmp_fd, &header, sizeof(header));
	read(bmp_fd, &info, sizeof(info));
 
 
    
    // 第三个结构体是可选的，需要判断是否存在
	if(info.compression != 0)
	{
		read(bmp_fd, &quad, sizeof(quad));
		fprintf(stderr, "read quad! \n");
	}
 
    // 输出图片相关信息
    printf("图片大小: %d\n", header.size);
    printf("图片尺寸: %d×%d\n", info.width, info.height);
    printf("色深: %dbpp\n", info.bit_count);
 
    int width = info.width;
    int height = info.height;
 
 
    if((start_x+width) > 800  || (start_y+height) > 480)
    {
        printf("图片越界,无法显示\n");
        close(bmp_fd);
        return -1;
    }
 
 
    //定义存储图片所有RGB数据的缓冲区
    unsigned char *bmp_buff = (unsigned char *)malloc(width*height*3);
    unsigned int *buff = (unsigned int *)malloc(width*height*4);
 
    //将图片的像素存放bmp_buf当
    read(bmp_fd, bmp_buff, width*height*3);
 
    for(i=0; i<width*height; i++)
    {
        buff[i] = bmp_buff[3*i+0]|bmp_buff[3*i+1]<<8|bmp_buff[3*i+2]<<16;
    }   
 
    //像素点填充到屏幕
    for(y=0; y<height; y++)
    {
        for(x=0; x<width; x++) 
        {
            mem_p[(start_y+y)*800+start_x+x] = buff[(height-1-y)*width+x];
        }
    }
 
 
    free(bmp_buff);
 
    free(buff);
    close(bmp_fd);
 
}